What's New in Edge Rails: No More Formatted Routes 8

Posted by ryan
at 8:45 PM on Wednesday, November 26, 2008

This feature is scheduled for: Rails v2.3/3.0

Parallel to the addition of :except and :only options to Rails’ routing comes the removal of all those formatted_xxx named routes. Turns out these routes ate up a lot of memory and served minimal purpose.

So with this routes.rb definition:

1
2
3
ActionController::Routing::Routes.draw do |map|
  map.resources :articles
end

you’ll no longer have any of the formatted_xxx url helpers available. However, you will be able to get the same functionality by passing in a :format option to the base view helper methods:

1
2
3
4
# Old => New
formatted_article_path(article, :xml) => article_path(article, :format => :xml)
formatted_new_article_path(:json) => new_article_path(:format => :json)
# etc...

A minimal change that will have a big impact on the memory consumption of each of your Rails processes – especially if you’ve got a complex app with lots of routes.

If you’re on Edge Rails you’re going to start seeing deprecation warnings where you reference these formatted url helpers, so you’ve got some warning before you have to make the switch.

tags: ruby, rubyonrails

What's New in Edge Rails: Object.try 11

Posted by ryan
at 9:49 PM on Wednesday, November 19, 2008

This feature is scheduled for: Rails v2.3/3.0

Those of you using Chris Wanstrath’s slick little try trick will now have access to that functionality in Rails with this ActiveSupport update.

Basically, try lets you attempt to invoke a method on an object without worrying about a NoMethodError being raised. If the method doesn’t exist, or if the target object nil, then nil will be returned without exceptions:

1
2
3
4
5
# No exceptions when receiver is nil
nil.try(:destroy) #=> nil

# Useful when chaining potential nil items
User.admins.first.try(:address).try(:reset)

Just a small little bit of syntactical candy pulled in from the community.

Update: You can now also use this trick for methods with arguments

tags: ruby, rubyonrails

What's New in Edge Rails: render Stops Being High-Maintenance 4

Posted by ryan
at 9:33 PM on Wednesday, November 19, 2008

This feature is scheduled for: Rails v2.3/3.0

render is one of those oft-used view-helper methods that always seems to be a little cumbersome. The most common use is to render a partial within another view:


render :partial => 'articles/article', :locals => { :article => @article }

But this is a lot of work for a simple operation, and now it becomes much simpler. Now the default is to assume that a partial is requested (in the past render a :file was the default) and that the final hash argument is the locals hash. Here is the above functionality using the new syntax:

1
2
3
4
5
6
7
8
9
# Render the 'article' partial with an article local variable
render 'articles/article', :article => @article

# Or even better (same as above)
render @article

# And for collections, same as:
# render :partial => 'articles/article', :collection => @articles
render @articles

If you’ve got some old render calls hanging around that aren’t using partials you’ll have to specify the :file option now:


render :file => 'original'

Hassle free partial rendering. Yay.

tags: ruby, rubyonrails

What's New in Edge Rails: Application.rb Duality is no More 7

Posted by ryan
at 8:36 PM on Tuesday, November 18, 2008

This feature is scheduled for: Rails v2.3/3.0

Ever wonder why your ArticlesController has to live in a file called articles_controller.rb but that special ApplicationController gets to hang out in application.rb? Well no more.. That snooty little controller will now live in application_controller.rb and abide by the same naming restrictions that the rest of us do.

By the time Rails 2.3/3.0 rolls around this rename will happen for you as part of rake rails:update. However, for those of you on Edge Rails, you’ll need to make this change yourself.

tags: ruby, rubyonrails

What's New in Edge Rails: Default Scoping 26

Posted by ryan
at 4:51 PM on Tuesday, November 18, 2008

This feature is scheduled for: Rails v2.3/3.0

It’s pretty common to want SQL queries against a particular table to always be sorted the same way, and is one of the reasons why I added the ordered scope to the utility scopes gem. For instance, when dealing with collections of articles it is reasonable to expect that the default ordering be most recent first, i.e. created_at DESC. Well now you can specify default ordering, and other scopes, in edge rails directly in your ActiveRecord model.

Taking our Article example let’s specify the aforementioned default ordering:

1
2
3
class Article < ActiveRecord::Base
  default_scope :order => 'created_at DESC'
end

Now, when any find method or named_scope is executed the default ordering comes along for the ride:


Article.find(:all) #=> "SELECT * FROM `articles` ORDER BY created_at DESC"

The same holds true for any named scopes you might have:

1
2
3
4
5
6
class Article < ActiveRecord::Base
  default_scope :order => 'created_at DESC'
  named_scope :published, :conditions => { :published => true }
end

Article.published #=> "SELECT * FROM `articles` WHERE published = true ORDER BY created_at DESC"

There are some things to keep in mind, however. First is that scopes like :join, :offset, :limit and :order will get clobbered by the innermost rule. For example, here the default scope ordering loses out to the named_scope ordering.

1
2
3
4
5
6
7
8
9
class Article < ActiveRecord::Base
  default_scope :order => 'created_at DESC'
  named_scope :published, :conditions => { :published => true },
                          :order => 'published_at DESC'
end

# published_at DESC clobbers default scope
Article.published
    #=> "SELECT * FROM `articles` WHERE published = true ORDER BY published_at DESC"

Also keep in mind that the default scoping is inherited, so child-classes of Article will have the same default scoping.

And for those occasions when you want to override or remove your default scope, just use with_exclusive_scope:

1
2
3
4
5
6
class Article < ActiveRecord::Base
  default_scope :order => 'created_at DESC'
end

# Ignore other scoping within this block
Article.with_exclusive_scope { find(:all) }  #=> "SELECT * FROM `articles`

default_scope is a great way to specify reasonable query defaults and relieve yourself of having to create your own named_scopes for that purpose or specify such conditions at every invocation. Yes, I just said ‘relieve yourself’... giggity giggity

tags: ruby, rubyonrails

What's New in Edge Rails: :except and :only Routing Options 19

Posted by ryan
at 9:20 AM on Thursday, November 13, 2008

This feature was released in: Rails v2.2.1

Just added to Edge Rails is the ability to exclude and include the default generated routes in your mapping configuration. Previously, map.resources :articles would generate routes to all seven default actions on the ArticlesController (index, create, new, edit, show, update, destroy). You can now tell your routes configuration to only generate a subset of those actions, or to exclude a subset of those actions:

1
2
3
4
5
6
7
8
# Only generate the :index route of articles
map.resources :articles, :only => :index

# Generate all but the destroy route of articles
map.resources :articles, :except => :destroy

# Only generate the non-modifying routes of articles
map.resources :articles, :only => [:index, :show]

Note that you can use the :all and :none values to denote all or none of the default routes.

1
2
3
4
5
# Don't generate any default article routes, just the approve route
map.resources :articles, :except => :all, :member => { :approve => :put }

# Same
map.resources :articles, :only => :none, :member => { :approve => :put }

You should also note that these options will be inherited by nested resources that don’t override them. For instance, in this example, comments would only have the :index and :show routes exposed:

1
2
3
4
5
# Because comments are nested within articles, they too will only
# have the index and show routes generated.
map.resources :articles, :only => [:index, :show] do |article|
  article.resources :comments
end

Specifying either an :except or :only option in a nested resource will override its parent resource’s options. E.g. this routing will result in comments having all actions but :show routed:

1
2
3
4
5
map.resources :articles, :only => [:index, :show] do |article|

  # My :except option overrides my parent resources :only
  article.resources :comments, :except => :show
end

The motivation behind this feature is that complex routing consumes a lot of memory. So eliminating unnecessary and unused routes can significantly reduce your memory consumption. The holy grail of this, however, will be when you can specify which routes should be formatted (json, xml etc…) as this is essentially doubling the number of default routes when very rarely does every call need to be API-accessible.

tags: ruby, rubyonrails

Roxy: A Ruby Proxy-Object Library 26

Posted by ryan
at 4:27 PM on Monday, November 10, 2008

Proxies are a powerful tool in software development, allowing you to transparently provide extra functionality or a slight abstraction to an underlying object. One of the more visible uses of proxies is in ActiveRecord which uses a proxy to represent its many associations. For instance, in the following article definition:

1
2
3
class Article < ActiveRecord::Base
  has_many :comments
end

when you call article.comments what you get back is actually a proxy object that wraps the comments collection with some extra functionality like the << and build methods. Although it looks like a normal Array when you directly access comments, it’s really a proxy that’s marshaling method calls to an underlying collection or intercepting the method calls if it’s functionality it wants to handle itself. Named scopes also work in a similar manner.

That’s great and all, but these proxies are tied very specifically to their implementations within ActiveRecord .. and that’s what Roxy is intended to address. Roxy brings some serious moxie to your development with the ability to easily define and use proxies in your non ActiveRecord classes.

Let’s take a look at an example: Suppose I have a person object that has a list of parents and children (again, this is outside the scope of ActiveRecord or any other persistence framework where you might be able to do this with some other mechanism).

1
2
3
class Person
  attr_accessor :first, :last, :parents, :children
end

If you want add functionality to a person that determines if their parents are divorced, or if they have any stepchildren you could easily enough add that functionality directly to the Person object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person
  attr_accessor :first, :last, :parents, :children
  
  def initialize(first, last)
    @first, @last = first, last
  end

  # If my parents have different last names, then assume they're divorced
  def parents_divorced?
      parents.size > 1 and parents.collect { |parent| parent.last }.uniq.size > 1
  end

  # Any child with a different last name than mine is considered
  # a step-child.
  def step_children
    children.select { |child| self.last != child.last }
  end
end

but this approach has always seemed very obtuse, however. If I am strictly modeling my domain to the real world, which is the approach I favor until it becomes unwieldy to do so, what I really want to do is ask a person’s parents if they’re divorced. After all, their divorce status is a property of the parents, not the person itself. With Roxy this structure is easy to model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
require 'roxy'
class Person

  # Add in proxy ability
  include Roxy::Moxie

  attr_accessor :first, :last, :parents, :children
  
  # Add ability to ask the parents collection if they are divorced
  proxy :parents do    
    def divorced?
      proxy_target.size > 1 and
        proxy_target.collect { |parent| parent.last }.uniq.size > 1
    end
  end
  
  # Add ability to ask the children collection for only the step-children
  proxy :children do
    def step
      proxy_target.select { |child| proxy_owner.last != child.last }
    end    
  end    
  
  def initialize(first, last)
    @first, @last = first, last
  end
end

# Now the following is possible:
person.parents.divorced? #=> true|false
person.children.step #=> [<Person...>, <Person...>]

Roxy allows you transparently adorn existing attributes and methods with added functionality, making a more realistic domain model. This is very similar to rails’ association proxies except that you are now free to add functionality to all methods and objects.

Proxy methods are defined in the block that is passed to the proxy call. Within each proxy method you can reference the object that owns the proxy (the person instance here) as proxy_owner and the thing that is being proxied (the parents and children collections here) as proxy_target.

Advanced

You’re not limited to proxying existing methods, you can just as easily proxy to another object using the :to option.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require 'roxy'
class Person

  # Add in proxy ability
  include Roxy::Moxie

  attr_accessor :address

  proxy :shipping, :to => ShippingMethod.all do
    def cheapest
      proxy_target.min { |m| m.cost_from(proxy_owner.address) }
    end
  end
end

# Find the cheapest shipping method from all methods
person.shipping.cheapest #=> <ShippingMethod...>

If the value you want to proxy needs to be evaluated at runtime just pass in a proc. The proc should accept a single argument which will be the proxy_owner instance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require 'roxy'
class Person

  # Add in proxy ability
  include Roxy::Moxie

  attr_accessor :address

  proxy :shipping, :to => proc { |person| ShippingMethod.available_for(person) } do
    def cheapest
      proxy_target.min { |m| m.cost_from(proxy_owner.address) }
    end
  end
end

# Find the cheapest shipping method from the methods
# only available to 'person'
person.shipping.cheapest #=> <ShippingMethod...>

You’ll notice that the best use of proxies is as a lightweight relationship between two things. I.e. instead of creating a whole other object to represent the relationship between a person and the various shipping methods you can quickly add functionality directly to that object-relationship as a proxy method.

A sign of abuse of this particular proxy pattern is when you reference only one of the proxy_owner or proxy_target and neither depends on the other in any way. That is usually an indication that the functionality should live solely in the referenced proxy owner/target and not in the proxy itself.

Proxy methods can also be defined as modules (as in Rails’ association extensions) for greater re-use between similar proxies with the :extend option (which can take one or more modules):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'roxy'
class Person

  # Add in proxy ability
  include Roxy::Moxie

  attr_accessor :age, :children, :parents

  # Re-usable functionality to find the oldest person in a collection
  module PersonCollection
    def oldest
      proxy_target.max { |p| p.age }
    end
  end

  proxy :children, :extend => PersonCollection
  proxy :parents, :extend => PersonCollection

end

# Now the following is possible:
person.parents.oldest #=> <Person...>
person.children.oldest #=> <Person...>

Once you grasp the beauty, simplicity and power of proxies you’ll likely find many uses for them. They’re a great tool to have in your toolbox and Roxy would love it if she found a place in yours.

Update: Nov. 14th, 2008: Based on your feedback I just updated the gem (v0.2) to properly handle proxying methods that have arguments:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Person
  
  include Roxy::Moxie
  
  # Contrived example to show that reload value is passed through
  # to the proxy target
  def ancestors(reload = false)
    1.upto(4).to_a.collect { |i| "#{reload ? 'r' : ''}ancestor#{i}" }      
  end
  
  # Men = odd numbered ancestors
  proxy :ancestors do
    def men
      proxy_target.select { |a| a.include?('1') || a.include?('3') }
    end
    def women
      proxy_target - men
    end
  end

end

# Note how arg is passed through (reloaded ancestors start
# with 'r')
person.ancestors(true).men #=> ['rancestor1', 'rancestor3']

# Even default arg is retained (default is false, so no
# ancestors have 'r' prefix)
person.ancestors.men #=> ['ancestor1', 'ancestor3']

Installation

Installing is pretty simple…

sudo gem install yfactorial-roxy --source http://gems.github.com

Teaser

I hope to have an extension library up soon that utilizes Roxy to provide ActiveRecord-like association definitions in ActiveResource. Something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
require 'roxy'
class User < ActiveResource::Base

  include Roxy::Moxie

  proxy :articles,
            :to => proc { |u| Article.find(:all, :params => { :user_id => u.id } } do
    def destroy_all
      proxy_target.each { |a| a.destroy }
    end
  end
end

# Now a remote user looks a lot like a first class active record object:
user.articles #=> [<Article...>, <Article...>, ...]
user.articles.destroy_all

Stay tuned for that, and let me know where you’ve found proxies to be a great tool to have around. I’m always looking for better example scenarios.

tags: ruby

What's New in Edge Rails: Even Better Conditional GET Support

Posted by ryan
at 9:13 PM on Friday, October 24, 2008

This feature was released in Rails v2.2.1

We talked about the new conditional GET support in rails a couple months ago. As some of the comments alluded, the feature was somewhat cumbersome to use – especially by Ruby standards. Well, the feature has since been refined. So, read the original post to get the gist and come back here for the sugar.

Instead of manually setting properties directly on the response and querying the request to see if it’s fresh we have some higher-level accessors we can use. Observe (extending our previous example):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ArticlesController < ApplicationController

  def show
    @article = Article.find(params[:id])

    # If the request is stale according to the given timestamp and etag value
    # (i.e. it needs to be processed again) then execute this block
    if stale?(:last_modified => @article.published_at.utc, :etag => @article)
      respond_to do |wants|
        # ... normal response processing
      end
    end

    # If the request is fresh (i.e. it's not modified) then you don't need to do
    # anything. The default render checks for this using the parameters
    # used in the previous call to stale? and will automatically send a
    # :not_modified.  So that's it, you're done.
end

If you don’t have any special response processing and are using the default rendering mechanism (i.e. you’re not using respond_to or calling render yourself) then you’ve got an easy helper in fresh_when:

1
2
3
4
5
6
7
8
9
class ArticlesController < ApplicationController

  # This will automatically send back a :not_modified if the request is fresh, and
  # will render the default template (article.*) if it's stale.
  def show
    @article = Article.find(params[:id])
    fresh_when :last_modified => @article.published_at.utc, :etag => @article
  end
end

There you have it, the new and improved conditional GET support in Rails 2.2.

tags: ruby, rubyonrails

What's New in Edge Rails: Rails 2.2 Released - Summary of Features 5

Posted by ryan
at 8:13 PM on Friday, October 24, 2008

I’ve updated this post to reflect the features added after RC1, so consider this to be a full list of major 2.2 features

Rails 2.2 (RC1) was just released, let’s take a peek at some of the major new features (as determined by yours truly – feel free to pipe up with features that I’ve missed).

Rails 2.2.2 requires rubygems v 1.3.1, so before you upgrade make sure to do a sudo gem update --system.

Rails 2.2 Features

Note: The nested model mass assignment feature previously discussed has been delayed until after this 2.2 release. Just needs a little more time in the oven, apparently.

Enjoy, folks.

tags: ruby, rubyonrails

What's New in Edge Rails: You Smell That?

Posted by ryan
at 4:43 PM on Thursday, October 23, 2008

... smells like Rails v2.2 is right around the corner…

tags: ruby, rubyonrails